#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
#include <assert.h>

#include "lambda.h"

typedef struct {
    uint8_t addr[6];
} BD_ADDR_T;

typedef struct{
    int a;
}test_t;


void fun0(){
    printf("fun0\n");
}

void fun1(int a){
    printf("fun1 %d\n", a);
}

void fun2(BD_ADDR_T* addr){
    printf("fun2 %d %d\n", addr->addr[0], addr->addr[1]);
}

void fun3(int a, int b, int c){
    printf("fun3 %d %d %d\n", a, b, c);
}

void fun4(int a, int b, int c, int d){
    printf("fun4 %d %d %d %d\n", a, b, c, d);
}

typedef struct {
    app_func_t fn;
    void *arg;
} task_msg_t;

#define MAX_QUEUE_SIZE 100

typedef struct {
    task_msg_t queue[MAX_QUEUE_SIZE];
    int front;
    int rear;
    pthread_mutex_t mutex;
    pthread_cond_t not_empty;
    pthread_cond_t not_full;
    int running;
} task_queue_t;

static task_queue_t g_task_queue = {
    .front = 0,
    .rear = 0,
    .running = 1
};

void task_queue_init(void) {
    pthread_mutex_init(&g_task_queue.mutex, NULL);
    pthread_cond_init(&g_task_queue.not_empty, NULL);
    pthread_cond_init(&g_task_queue.not_full, NULL);
}
task_msg_t * task_queue_push(task_msg_t *msg) {
    task_msg_t *ret;
    pthread_mutex_lock(&g_task_queue.mutex);
    while ((g_task_queue.rear + 1) % MAX_QUEUE_SIZE == g_task_queue.front) {
        pthread_cond_wait(&g_task_queue.not_full, &g_task_queue.mutex);
    }
    g_task_queue.queue[g_task_queue.rear] = *msg;
    ret = &g_task_queue.queue[g_task_queue.rear];
    g_task_queue.rear = (g_task_queue.rear + 1) % MAX_QUEUE_SIZE;
    pthread_cond_signal(&g_task_queue.not_empty);
    pthread_mutex_unlock(&g_task_queue.mutex);

    return ret;
}

int task_queue_pop(task_msg_t *msg) {
    pthread_mutex_lock(&g_task_queue.mutex);
    while (g_task_queue.front == g_task_queue.rear && g_task_queue.running) {
        pthread_cond_wait(&g_task_queue.not_empty, &g_task_queue.mutex);
    }
    if (!g_task_queue.running) {
        pthread_mutex_unlock(&g_task_queue.mutex);
        return 0;
    }
    *msg = g_task_queue.queue[g_task_queue.front];
    g_task_queue.front = (g_task_queue.front + 1) % MAX_QUEUE_SIZE;
    pthread_cond_signal(&g_task_queue.not_full);
    pthread_mutex_unlock(&g_task_queue.mutex);
    return 1;
}

void* thread_func(void* arg) {
    task_msg_t msg;
    while (task_queue_pop(&msg)) {
        if (msg.fn) {
           msg.fn(msg.arg);
        }
    }
    return NULL;
}

void app_post(app_func_t fn, void *arg)
{
    task_msg_t msg = {
        .fn = fn,
        .arg = arg
    };
    task_queue_push(&msg);
}

void* app_post_delay(app_func_t fn, void *arg, uint32_t delay)
{
    task_msg_t msg = {
        .fn = fn,
        .arg = arg
    };
    return task_queue_push(&msg);
}

void app_cancel_delay(void *handle)
{
    // do nothing
}

void app_task_cleanup(void)
{
    pthread_mutex_lock(&g_task_queue.mutex);
    g_task_queue.running = 0;
    pthread_cond_broadcast(&g_task_queue.not_empty);
    pthread_mutex_unlock(&g_task_queue.mutex);
}

void go()
{
    test_t* test = (test_t*)malloc(sizeof(test_t));
    test->a = 10;

    BD_ADDR_T addr = {1, 2, 3, 4, 5, 6};
    typeof(&addr) p = &addr;
    printf("sizeof(PARAM_TYPE(&addr)) = %ld\n", sizeof(PARAM_TYPE(p)));

    APP_RUN(printf("Hello World\n"));

    APP_RUN({
        printf("Hello World\n");
    });

    APP_RUN_EXT({
        printf("Hello World %d\n", test->a);
        free(test);
    }, test);

    void* timer1 = APP_RUN_DELAY({
        printf("Hello World\n");
    }, 1000);

    APP_CANCEL_DELAY(timer1);

    APP_POST(fun0);

    int a = 10;
    APP_POST(fun1, a);
    APP_POST(fun1, 11);
    APP_POST(fun2, &addr);
    APP_POST(fun3, 1, 2, 3);
    APP_POST(fun4, 1, 2, 3, 4);
}

void main(){
    pthread_t thread_id;

    task_queue_init();
    pthread_create(&thread_id, NULL, thread_func, NULL);

    go();

    sleep(1);

    app_task_cleanup();
    pthread_join(thread_id, NULL);
}